/**
 * 
 */
package ro.dta.idbi.ui.patterns;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JLabel;

import ro.dta.idbi.interfaces.sql.ISqlColumn;
import ro.dta.idbi.interfaces.sql.ISqlConstruct;
import ro.dta.idbi.interfaces.sql.ISqlForeignKey;
import ro.dta.idbi.interfaces.sql.ISqlTable;
import ro.dta.idbi.interfaces.ui.IMainFrame;
import ro.dta.idbi.patterns.NormaliseRev;
import ro.dta.idbi.ui.sql.SqlForeignKeyCell;
import ro.dta.idbi.ui.sql.SqlGraph;
import ro.dta.idbi.ui.sql.SqlTableCell;
import uk.ac.ic.doc.automed.reps.Schema;

import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.swing.mxGraphComponent;

/**
 * UI for the <i>reverse normalisation pattern</i>
 * 
 * @author Tudor Dobrila
 * 
 */
public class NormaliseRevDialog extends AbstractPatternDialog {

	private static final long serialVersionUID = 8406321004343614428L;
	private static final Logger LOGGER = Logger
			.getLogger("ro.dta.idbi.ui.patterns.NormaliseRevDialog");
	
	/**
	 * Check if the current pattern can be applied over a given set of objects
	 * 
	 * Used to check if the items selected by the user in the UI can be used to
	 * apply this transformation pattern over them
	 * 
	 * @param schema
	 *            Schema in the context of which to check the validity of the
	 *            array of objects
	 * @param objects
	 *            Set of objects
	 * @return True if pattern can be applied over set of objects, false
	 *         otherwise
	 */
	public static boolean isValid(final Schema schema, final Object[] objects) {
		if (objects.length != 2) {
			return false;
		}

		// Both items selected have to be tables
		if (!(objects[0] instanceof ISqlTable) || !(objects[1] instanceof ISqlTable)) {
			return false;
		}

		ISqlTable table1 = (ISqlTable) objects[0];
		ISqlTable table2 = (ISqlTable) objects[1];

		// Check if there is a FK constraint from one table to the other's PK
		// columns
		return checkTable(table1, table2) != null || checkTable(table2, table1) != null;
	}

	/**
	 * Check if <i>table1</i> has a foreign key pointing to <i>table2</i>'s PK
	 * columns
	 * 
	 * @return True if <i>table1</i> has a foreign key pointing to
	 *         <i>table2</i>'s PK columns, false otherwise
	 */
	public static ISqlForeignKey checkTable(ISqlTable table1, ISqlTable table2) {
		List<ISqlForeignKey> fks = table1.getForeignKeys();
		List<ISqlColumn> pks = table2.getPrimaryKey();

		for (ISqlForeignKey fk : fks) {
			if (fk.getTarget().getValue().equals(table2.getValue())) {
				List<ISqlColumn> targetCols = fk.getTargetCols();

				if (pks.size() == targetCols.size()) {
					boolean isOk = true;
					for (ISqlColumn c : targetCols) {
						if (!pks.contains(c)) {
							isOk = false;
						}
					}

					if (isOk) {
						return fk;
					}
				}
			}
		}

		return null;
	}
	
	private ISqlTable targetTbl;
	private ISqlTable sourceTbl;
	private List<ISqlColumn> detSet;
	private List<ISqlColumn> depSet;

	/**
	 * Reverse normalisation pattern dialog class constructor
	 * 
	 * @param parentFrame
	 *            Parent frame of this dialog
	 * @param schema
	 *            Schema over which the transformation is applied
	 * @param element
	 *            Sql construct(s) over which the transformation is applied
	 * @param beforeLabel
	 *            Label for the before diagram
	 * @param afterLabel
	 *            Label for the transformed diagram
	 */
	public NormaliseRevDialog(final IMainFrame parentFrame, final Schema schema,
			final ISqlConstruct[] element, final JLabel beforeLabel, final JLabel afterLabel) {
		super(parentFrame, schema, element, beforeLabel, afterLabel);

		if (!isValid(schema, element)) {
			throw new IllegalArgumentException(
					"This pattern can't be applied to the given elements");
		}
	}

	@Override
	protected mxGraphComponent createOriginalDiagram() {
		initialise();
		final SqlGraph graph = new SqlGraph();
		graph.getModel().beginUpdate();

		try {
			final SqlTableCell newSource = (SqlTableCell) sourceTbl.clone();
			mxGeometry newGeometry = new mxGeometry(5, 5,
					sourceTbl.getGeometry().getWidth(), sourceTbl.getGeometry().getHeight());
			newSource.setGeometry(newGeometry);
			newSource.layout(graph);
			
			final SqlTableCell newTarget = (SqlTableCell) targetTbl.clone();
			newGeometry = new mxGeometry(5, 5,
					targetTbl.getGeometry().getWidth(), targetTbl.getGeometry().getHeight());
			newTarget.setGeometry(newGeometry);
			newTarget.setStyle("SEL_TABLE");
			newTarget.layout(graph);
			
			SqlForeignKeyCell foreignKey = new SqlForeignKeyCell(null, null, "", "", newSource, newTarget, "FK_EDGE");
			graph.addCell(foreignKey, graph.getDefaultParent());
		} catch (CloneNotSupportedException e) {
			LOGGER.log(Level.SEVERE, "Fatal exception", e);
		} finally {
			mxHierarchicalLayout layout = new mxHierarchicalLayout(graph);
			layout.execute(graph.getDefaultParent());
			graph.setAutoSizeCells(true);
			graph.getModel().endUpdate();
		}

		return new mxGraphComponent(graph);
	}

	@Override
	protected mxGraphComponent createTransformedDiagram() {
		final SqlGraph graph = new SqlGraph();
		graph.getModel().beginUpdate();

		try {
			final SqlTableCell newSource = (SqlTableCell) sourceTbl.clone();
			mxGeometry newGeometry = new mxGeometry(5, 5,
					sourceTbl.getGeometry().getWidth(), sourceTbl.getGeometry().getHeight());
			
			for (ISqlColumn c : depSet) {
				ISqlColumn newCol = (ISqlColumn) c.clone();
				newSource.addColumn(newCol);
			}
			
			newSource.setGeometry(newGeometry);
			newSource.relayout(graph);
		} catch (CloneNotSupportedException e) {
			LOGGER.log(Level.SEVERE, "Fatal exception", e);
		} finally {
			mxHierarchicalLayout layout = new mxHierarchicalLayout(graph);
			layout.execute(graph.getDefaultParent());
			graph.setAutoSizeCells(true);
			graph.getModel().endUpdate();
		}

		return new mxGraphComponent(graph);
	}

	/**
	 * Initialise the dialog
	 */
	private void initialise() {
		ISqlTable table1 = (ISqlTable) element[0];
		ISqlTable table2 = (ISqlTable) element[1];
		
		ISqlForeignKey foreignKey = checkTable(table1, table2);
		if (foreignKey != null) {
			sourceTbl = table1;
			targetTbl = table2;
		} else {
			foreignKey = checkTable(table2, table1);
			sourceTbl = table2;
			targetTbl = table1;
		}
		
		detSet = foreignKey.getSourceCols();
		depSet = new ArrayList<ISqlColumn>();
		List<ISqlColumn> cols = targetTbl.getColumns();
		for (ISqlColumn c : cols) {
			if (!c.isPK()) {
				depSet.add(c);
			}
		}
		
		StringBuilder macro = new StringBuilder(NormaliseRev.COMMAND);
		macro.append(" (<<").append(sourceTbl.getValue()).append(">>, <<");
		macro.append(targetTbl.getValue()).append(">>, ");
		macro.append(setToString(detSet));
		macro.append(", ");
		macro.append(setToString(depSet));
		macro.append(")");
		macroField.setText(macro.toString());
	}
	
	/**
	 * Convert a set of columns into textual representation
	 * @param cols Set of column
	 * @return Textual representation of the set of column
	 */
	private String setToString(List<ISqlColumn> cols) {
		StringBuilder builder = new StringBuilder();
		if (cols.size() > 1) {
			builder.append("{");
		}
		for (ISqlColumn c : cols) {
			String colName = c.getValue().toString().replace("?", "");
			builder.append(colName).append(", ");
		}
		builder.setLength(builder.length() - 2);
		if (cols.size() > 1) {
			builder.append("}");
		}
		
		return builder.toString();
	}
}
